#include "OgreGLESHardwareBufferManager.h"
#include "OgreGLESHardwareVertexBuffer.h"
#include "OgreGLESHardwareIndexBuffer.h"
#include "OgreHardwareBuffer.h"

namespace Ogre
{
	#define SCRATCH_POOL_SIZE 1 * 1024 * 1024
	#define SCRATCH_ALIGNMENT 32

	struct GLESScratchBufferAlloc
	{
		uint32 size: 31;
		uint32 free: 1;
	};

	GLESHardwareBufferManagerBase::GLESHardwareBufferManagerBase()
	{
		mScratchBufferPool = static_cast<char*>(OGRE_MALLOC_ALIGN(SCRATCH_POOL_SIZE,
																  MEMCATEGORY_GEOMETRY,
																  SCRATCH_ALIGNMENT));
		GLESScratchBufferAlloc* ptrAlloc = (GLESScratchBufferAlloc*)mScratchBufferPool;
		ptrAlloc->size = SCRATCH_POOL_SIZE - sizeof(GLESScratchBufferAlloc);
		ptrAlloc->free = 1;
	}

	GLESHardwareBufferManagerBase::~GLESHardwareBufferManagerBase()
	{
		destroyAllDeclarations();
		destroyAllBindings();

		OGRE_FREE_ALIGN(mScratchBufferPool, MEMCATEGORY_GEOMETRY, SCRATCH_ALIGNMENT);
	}

	HardwareVertexBufferSharedPtr
		GLESHardwareBufferManagerBase::createVertexBuffer(size_t vertexSize,
														size_t numVerts,
														HardwareBuffer::Usage usage,
														bool useShadowBuffer)
	{
		GLESHardwareVertexBuffer* buf = 
			OGRE_NEW GLESHardwareVertexBuffer(this, vertexSize, numVerts, usage, true);
		{
			OGRE_LOCK_MUTEX(mVertexBuffersMutex)
			mVertexBuffers.insert(buf);
		}
		return HardwareVertexBufferSharedPtr(buf);
	}

	HardwareIndexBufferSharedPtr GLESHardwareBufferManagerBase::createIndexBuffer(HardwareIndexBuffer::IndexType itype,
																				size_t numIndexes,
																				HardwareBuffer::Usage usage,
																				bool useShadowBuffer)
	{
		GLESHardwareIndexBuffer* buf = 
			OGRE_NEW GLESHardwareIndexBuffer(this, itype, numIndexes, usage, true);
		{
			OGRE_LOCK_MUTEX(mIndexBuffersMutex)
			mIndexBuffers.insert(buf);
		}
		return HardwareIndexBufferSharedPtr(buf);
	}

	RenderToVertexBufferSharedPtr GLESHardwareBufferManagerBase::createRenderToVertexBuffer()
	{
		return RenderToVertexBufferSharedPtr();
	}

	GLenum GLESHardwareBufferManagerBase::getGLUsage(unsigned int usage)
	{
		switch(usage)
		{
		case HardwareBuffer::HBU_STATIC:
		case HardwareBuffer::HBU_STATIC_WRITE_ONLY:
			return GL_STATIC_DRAW;
		case HardwareBuffer::HBU_DYNAMIC:
		case HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY:
		case HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE:
		default:
			return GL_DYNAMIC_DRAW;
		};
	}

	GLenum GLESHardwareBufferManagerBase::getGLType(unsigned int type)
	{
		switch(type)
		{
		case VET_FLOAT1:
		case VET_FLOAT2:
		case VET_FLOAT3:
		case VET_FLOAT4:
			return GL_FLOAT;
		case VET_SHORT1:
		case VEC_SHORT2:
		case VEC_SHORT3:
		case VEC_SHORT4:
			return GL_SHORT;
		case VET_COLOUR:
		case VEC_COLOUR_ABGR:
		case VEC_COLOUR_ARGB:
		case VEC_UBYTE4:
			return GL_UNSIGNED_BYTE;
		default:
			return 0;
		};
	}

	void* GLESHardwareBufferManagerBase::allocateScratch(uint32 size)
	{
		OGRE_LOCK_MUTEX(mScratchMutex)

		if (size % 4 != 0)
		{
			size += 4 - (size % 4);
		}

		uint32 bufferPos = 0;
		while (bufferPos < SCRATCH_POOL_SIZE)
		{
			GLESScratchBufferAlloc* pNext = (GLESScratchBufferAlloc*)(mScratchBufferPool + bufferPos);
			if (pNext->free && pNext->size >= size)
			{
				if (pNext->size > size + sizeof(GLESScratchBufferAlloc))
				{
					uint32 offset = sizeof(GLESScratchBufferAlloc) + size;

					GLESScratchBufferAlloc* pSplitAlloc = (GLESScratchBufferAlloc*)
						(mScratchBufferPool + bufferPos + offset);
					pSplitAlloc->free = 1;

					pSplitAlloc->size = pNext->size - size - sizeof(GLESScratchBufferAlloc);

					pNext->size = size;
				}

				pNext->free = 0;

				return ++pNext;
			}

			bufferPos += sizeof(GLESScratchBufferAlloc) + pNext->size;
		}

		return 0;
	}

	void GLESHardwareBufferManagerBase::deallocateScratch(void* ptr)
	{
		OGRE_LOCK_MUTEX(mScratchMutex)

		uint32 bufferPos = 0;
		GLESScratchBufferAlloc* pLast = 0;
		while(bufferPos < SCRATCH_POOL_SIZE)
		{
			GLESScratchBufferAlloc* pCurrent = (GLESScratchBufferAlloc*)(mScratchBufferPool + bufferPos);

			if ((mScratchBufferPool + bufferPos + sizeof(GLESScratchBufferAlloc)) == ptr)
			{
				pCurrent->free = 1;

				if (pLast && pLast->free)
				{
					bufferPos -= (pLast->size + sizeof(GLESScratchBufferAlloc));
					pLast->size += pCurrent->size + sizeof(GLESScratchBufferAlloc);
					pCurrent = pLast;
				}

				uint32 offset = bufferPos + pCurrent->size + sizeof(GLESScratchBufferAlloc);
				if (offset < SCRATCH_POOL_SIZE)
				{
					GLESScratchBufferAlloc* pNext = (GLESScratchBufferAlloc*)(
						mScratchBufferPool + offset);
					if (pNext->free)
					{
						pCurrent->size += pNext->size + sizeof(GLESScratchBufferAlloc);
					}
				}

				return;
			}

			bufferPos += sizeof(GLESScratchBufferAlloc) + pCurrent->size;
			pLast = pCurrent;
		}

		assert(false && "Memory deallocation error");
	}
}
